using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Common.Utilities;
using Northwoods.Go;
using Northwoods.Go.Xml;
using Workflows.Components.Rules;

namespace Rules.Diagram
{

    /// <summary></summary>
    [Serializable]
    public class RuleDoc:GoDocument
    {
        #region fields
        private bool _IsDirty;
        private RuleNode _StartNode;
        private RuleNode _EndNode;
        #endregion

        /// <summary></summary>
        public void Initialize()
        {
            this.StartNode = new RuleNode(RuleNodeType.Start);
            this.StartNode.Text = "Start";
            this.StartNode.Position = new PointF(100, 100);
            RuleInitializer startData=new RuleInitializer();
            this.StartNode.UserObject = startData;
            Add(this.StartNode);

            this.EndNode = new RuleNode(RuleNodeType.End);
            this.EndNode.Text = "End";
            this.EndNode.Position = new PointF(this.StartNode.Position.X, this.StartNode.Position.Y + 200);
            RuleFinalizer endData=new RuleFinalizer();
            endData.FromNodeID = startData.RuleNodeID;
            this.EndNode.UserObject = endData;
            Add(this.EndNode);

            MakeLink(this.StartNode.BottomPort, this.EndNode.TopPort);

            this._IsDirty = true;
        }

        #region property
        /// <summary>
        /// 
        /// </summary>
        public bool IsDirty
        {
            get
            {
                return this._IsDirty;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public RuleNode StartNode
        {
            get { return _StartNode; }
            set { _StartNode = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public RuleNode EndNode
        {
            get { return _EndNode; }
            set { _EndNode = value; }
        }

        #endregion

        /// <summary>automatically assign "false" or "true" label on link coming out of a Conditional</summary>
        public RuleLink MakeLink(IGoPort a, IGoPort b)
        {
            RuleLink link = new RuleLink();
            link.FromPort = a;
            link.ToPort = b;
            Add(link);

            RuleNode n = a.Node as RuleNode;
            if (n != null && n.LeftPort == a && a.LinksCount == 1)
            {
                GoText lab = new GoText();
                lab.Selectable = false;
                lab.Text = "false";
                link.FromLabel = lab;
            }
            else if (n != null && n.RightPort == a && a.LinksCount == 1)
            {
                GoText lab = new GoText();
                lab.Selectable = false;
                lab.Text = "true";
                link.FromLabel = lab;
            }

            // explicitly reroute after adding to document, so as to avoid nodes
            link.CalculateStroke();
            return link;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="link"></param>
        public void InsertNodeOnLink(RuleNode node, RuleLink link)
        {
            IGoPort from = link.FromPort;
            IGoPort to = link.ToPort;
            switch (node.Kind)
            {
                case RuleNodeType.Effect:
                case RuleNodeType.Action:
                    {
                        Debug.Assert(node.TopPort != null && node.BottomPort != null);
                        // splice in the new node N, by adding a new link from the bottom of node N,
                        // going to the original LINK's ToPort
                        link.ToPort = node.TopPort;
                        MakeLink(node.BottomPort, to);
                        break;
                    }
                case RuleNodeType.Conditional:
                    {
                        Debug.Assert(node.TopPort != null && node.LeftPort != null && node.RightPort != null);
                        // add two links, a "false" link from the LeftPort, and a "true" link from the RightPort,
                        // both going to the original LINK's ToPort
                        link.ToPort = node.TopPort;
                        MakeLink(node.LeftPort, to);
                        MakeLink(node.RightPort, to);
                        break;
                    }
                case RuleNodeType.Output:
                    {
                        Debug.Assert(node.TopPort != null);
                        // just add a new link from the original LINK's FromPort to the new End node's TopPort
                        MakeLink(from, node.TopPort);
                        break;
                    }
                default:
                    Debug.Fail("can't insert node " + node.GetToolTip(null));
                    break;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="newnode"></param>
        /// <param name="oldnode"></param>
        public void InsertNodeBeforeNode(RuleNode newnode, RuleNode oldnode)
        {
            switch (newnode.Kind)
            {
                case RuleNodeType.Action:
                case RuleNodeType.Effect:
                    {
                        Debug.Assert(newnode.TopPort != null && newnode.BottomPort != null);
                        GoCollection coll = new GoCollection();
                        foreach (IGoLink link in oldnode.TopPort.SourceLinks)
                        {
                            coll.Add(link.GoObject);
                        }
                        foreach (IGoLink link in coll)
                        {
                            link.ToPort = newnode.TopPort;
                        }
                        MakeLink(newnode.BottomPort, oldnode.TopPort);
                        break;
                    }
                case RuleNodeType.Conditional:
                    {
                        Debug.Assert(newnode.TopPort != null && newnode.LeftPort != null && newnode.RightPort != null);
                        // insert Conditional node subgraph before OLDNODE
                        GoCollection coll = new GoCollection();
                        foreach (IGoLink link in oldnode.TopPort.SourceLinks)
                        {
                            coll.Add(link.GoObject);
                        }
                        foreach (IGoLink link in coll)
                        {
                            link.ToPort = newnode.TopPort;
                        }
                        MakeLink(newnode.LeftPort, oldnode.TopPort);
                        MakeLink(newnode.RightPort, oldnode.TopPort);
                        break;
                    }
                case RuleNodeType.Output:
                    {
                        // start new branch off of some previous node
                        RuleNode prev = GetOtherEnd(oldnode.TopPort);
                        if (prev != null)
                        {
                            if (prev.Kind == RuleNodeType.Conditional)
                            {
                                //MakeLink(prev.LeftPort, newnode.TopPort);
                                MakeLink(prev.RightPort, newnode.TopPort);
                            }
                            else
                            {
                                MakeLink(prev.BottomPort, newnode.TopPort);
                            }
                        }
                        break;
                    }
                default:
                    Debug.Fail("can't insert node " + newnode.GetToolTip(null));
                    break;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        public void RemoveNode(RuleNode node)
        {
            if (node == null || !node.CanDelete()) return;
            switch (node.Kind)
            {
                case RuleNodeType.Action:
                case RuleNodeType.Effect:
                    {
                        Debug.Assert(node.TopPort != null && node.BottomPort != null);
                        RuleNode after = GetOtherEnd(node.BottomPort);
                        foreach (IGoLink link in node.TopPort.SourceLinks)
                        {
                            MakeLink(link.FromPort, after.TopPort);
                        }
                        node.Remove();
                        this._IsDirty = true;
                        break;
                    }
                case RuleNodeType.Conditional:
                    {
                        GoCollection coll = new GoCollection();
                        RuleNode join = GatherSubGraph(node, coll);
                        if (join != null)
                        {
                            foreach (IGoLink link in node.TopPort.SourceLinks)
                            {
                                MakeLink(link.FromPort, join.TopPort);
                            }
                        }
                        foreach (GoObject obj in coll) obj.Remove();
                        this._IsDirty = true;
                        break;
                    }
                case RuleNodeType.Output:
                    {
                        GoCollection coll = new GoCollection();
                        GatherEndBranch(node, coll);
                        foreach (GoObject obj in coll) obj.Remove();
                        this._IsDirty = true;
                        break;
                    }
                default:
                    Debug.Fail("can't remove node " + node.GetToolTip(null));
                    break;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        public RuleNode GetOtherEnd(IGoPort port)
        {
            if (port != null)
            {
                foreach (IGoLink link in port.Links)
                {
                    return link.GetOtherNode(port.Node) as RuleNode;
                }
            }
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        public IGoPort GetOtherPort(IGoPort port)
        {
            if (port != null)
            {
                foreach (IGoLink link in port.Links)
                {
                    return link.GetOtherPort(port);
                }
            }
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cond"></param>
        /// <param name="coll"></param>
        /// <returns></returns>
        public RuleNode GatherSubGraph(RuleNode cond, IGoCollection coll)
        {
            if (cond.Kind == RuleNodeType.Conditional)
            {
                Mark(GetOtherEnd(cond.LeftPort), 1);
                Mark(GetOtherEnd(cond.RightPort), 2);
                SelectMarked(cond, 1 | 2, coll);
                RuleNode join = FindJoin(cond, 1 | 2) as RuleNode;
                Clear(cond);
                return join;
            }
            return null;
        }

        private void Clear(IGoNode n)
        {
            if (n == null) return;
            n.UserFlags = 0;
            foreach (IGoNode x in n.Destinations)
            {
                Clear(x);
            }
        }

        private void Mark(IGoNode n, int i)
        {
            if (n == null) return;
            n.UserFlags |= i;
            foreach (IGoNode x in n.Destinations)
            {
                Mark(x, i);
            }
        }

        private void SelectMarked(IGoNode n, int i, IGoCollection coll)
        {
            if (n == null) return;
            if (n.UserFlags != i)
            {
                coll.Add(n.GoObject);
                foreach (IGoNode x in n.Destinations)
                {
                    SelectMarked(x, i, coll);
                }
            }
        }

        private IGoNode FindJoin(IGoNode n, int i)
        {
            if (n == null) return null;
            if (n.UserFlags == i) return n;
            foreach (IGoNode x in n.Destinations)
            {
                IGoNode r = FindJoin(x, i);
                if (r != null) return r;
            }
            return null;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="end"></param>
        /// <param name="coll"></param>
        public void GatherEndBranch(RuleNode end, IGoCollection coll)
        {
            if (end.Kind == RuleNodeType.End)
            {
                RuleNode n = end;
                while (n != null)
                {
                    IGoPort p = GetOtherPort(n.TopPort);
                    coll.Add(n);
                    if (p == null || p.LinksCount > 1)
                        break;
                    n = p.Node as RuleNode;
                }
            }
        }

        #region save
   
        /// <summary>
        /// 
        /// </summary>
        /// <param name="file"></param>
        public void Store(Stream file)
        {
            GoXmlWriter writer = new GoXmlWriter();
            writer.RootElementName = "flowchart";
            writer.AddTransformer(new TransformRuleNode());
            writer.AddTransformer(new TransformRuleLink());
            writer.NodesGeneratedFirst = true;
            writer.Objects = this;
            writer.Generate(file);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public List<RuleNode> Save()
        {
            List<RuleNode> allRuleNodes = new List<RuleNode>();
            allRuleNodes.Add(this.StartNode);
            this.NavigateNode(ref allRuleNodes, this.StartNode);
            allRuleNodes.Add(this.EndNode);
            this._IsDirty = false;
            return allRuleNodes;
        }

        private void NavigateNode(ref List<RuleNode> nodesVisited, RuleNode fromNode)
        {
            Guid fromRuleNodeID = Guid.Empty;
            string fromBranchName = string.Empty;
            RuleNodeDataBase fromRuleNodeData = (RuleNodeDataBase) fromNode.UserObject;
            fromRuleNodeID = fromRuleNodeData.RuleNodeID;
            
            if(fromNode.Links.Count>0)
            {
                foreach(GoObject linkObj in fromNode.Links)
                {
                    if(linkObj.GetType()==typeof(RuleLink))
                    {
                        RuleLink link = (RuleLink) linkObj;
                        if (link.FromNode == fromNode && link.ToNode != null)
                        {
                            RuleNode toNode = (RuleNode) link.ToNode;
                            if (toNode != this.EndNode && toNode.GetType() == typeof(RuleNode))
                            {
                                RuleNode toRuleNode = (RuleNode)toNode;
                                RuleNodeDataBase ruleNodeData = (RuleNodeDataBase)toRuleNode.UserObject;
                                ruleNodeData.FromNodeID = fromRuleNodeID;
                                if (link.FromPort == fromNode.LeftPort)
                                {
                                    fromBranchName = "false";
                                }
                                else if (link.FromPort == fromNode.RightPort)
                                {
                                    fromBranchName = "true";
                                }
                                else
                                {
                                    fromBranchName = ((RuleNodeDataBase) fromNode.UserObject).FromNodeBranch;
                                }
                                ruleNodeData.FromNodeBranch = fromBranchName;
                                nodesVisited.Add(toRuleNode);
                                this.NavigateNode(ref nodesVisited, toNode);
                            }
                        }
                    }
                }
            }
        }
        #endregion

        #region open
        /// <summary>
        /// 
        /// </summary>
        /// <param name="file"></param>
        public void Load(Stream file)
        {
            StartTransaction();
            Clear();
            GoXmlReader reader = new GoXmlReader();
            reader.AddTransformer(new TransformRuleNode());
            reader.AddTransformer(new TransformRuleLink());
            reader.RootObject = this;
            reader.UseDOM = false;
            reader.Consume(file);
            FinishTransaction("Load");
        }
        #endregion
    }
}
